package network

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"net"
	"os/exec"
	"strings"
)

var _ Driver = &BridgeDriver{}

type BridgeDriver struct {
}

func (b BridgeDriver) Name() string {
	return "bridge"
}

func (b BridgeDriver) Create(subnet, name string) (*Network, error) {
	cidr, ipNet, _ := net.ParseCIDR(subnet)
	ipNet.IP = cidr
	network := &Network{
		Name:   name,
		IPNet:  ipNet,
		Driver: b.Name(),
	}

	b.initBridge(network)
	return network, nil
}

func (b BridgeDriver) Delete(network *Network) error {
	link, err := netlink.LinkByName(network.Name)
	if err != nil {
		logrus.Infof("err: %v", err)
		return err
	}

	return netlink.LinkDel(link)
}

func (b BridgeDriver) Connect(network *Network, endPoint *EndPoint) error {
	link, err := netlink.LinkByName(network.Name)
	if err != nil {
		logrus.Infof("err: %v", err)
		return err
	}
	attrs := netlink.NewLinkAttrs()
	attrs.Name = endPoint.ID[:5]
	attrs.MasterIndex = link.Attrs().Index

	endPoint.Device = &netlink.Veth{
		LinkAttrs: attrs,
		PeerName:  "cif-" + endPoint.ID[:5],
	}

	netlink.LinkAdd(endPoint.Device)

	netlink.LinkSetUp(endPoint.Device)
	return nil
}

func (b BridgeDriver) Disconnect(network *Network, endPoint *EndPoint) error {
	panic("implement me")
}

func (b BridgeDriver) initBridge(network *Network) {
	createBridge(network.Name)

	setIP(network.Name, network.IPNet.String())

	setUP(network.Name)

	setupIPTables(network.Name, network.IPNet)
}

func setupIPTables(name string, ipNet *net.IPNet) {
	iptablesCmd := fmt.Sprintf("-t nat -A POSTROUTING -s %s ! -o %s -j MASQUERADE", ipNet.String(), name)
	cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)

	output, err := cmd.Output()
	if err != nil {
		logrus.Errorf("iptables Output, %v", output)
	}
}

func setUP(name string) {
	link, _ := netlink.LinkByName(name)
	netlink.LinkSetUp(link)
}

func setIP(name, rawIP string) {
	logrus.Infof("name: %v", name)
	logrus.Infof("rawIP: %v", rawIP)

	ipNet, _ := netlink.ParseIPNet(rawIP)

	addr := &netlink.Addr{
		IPNet: ipNet,
		Label: "",
		Flags: 0,
		Scope: 0,
		Peer:  nil,
	}

	logrus.Infof("addr: %v", addr)

	link, _ := netlink.LinkByName(name)

	list, _ := netlink.AddrList(link, 0)
	for _, a := range list {
		netlink.AddrDel(link, &a)
	}

	netlink.AddrReplace(link, addr)
}

func createBridge(name string) error {
	byName, err := net.InterfaceByName(name)
	logrus.Infof("byName: %v", byName)

	//list, err := netlink.LinkList()
	//for _, link := range list {
	//	logrus.Infof("link: %v", link)
	//}

	if err == nil {
		logrus.Infof("[%v] exists", name)
		return nil
	}

	if !strings.Contains(err.Error(), "no such network interface") {
		return err
	}

	linkAttrs := netlink.NewLinkAttrs()
	linkAttrs.Name = name

	bridge := &netlink.Bridge{
		LinkAttrs: linkAttrs}

	if err = netlink.LinkAdd(bridge); err != nil {
		return fmt.Errorf("create bridge err: %v", err)
	}
	return nil
}
